﻿using System;
using System.Collections.Generic;
using System.Threading;
using Pfz.Caching;
using Pfz.Extensions;
using Pfz.Threading;

namespace Pfz.Remoting.Internal
{
	internal sealed class _BidirectionalDictionary:
		ThreadSafeDisposable
	{
		private long _idGenerator;

		private sealed class Helper
		{
			internal Dictionary<object, long> _dictionary2 = new Dictionary<object, long>(ReferenceComparer.Instance);
		}

		internal Dictionary<long, object> _dictionary1 = new Dictionary<long, object>();
		private Dictionary<Thread, Helper> _helpers = new Dictionary<Thread, Helper>(ReferenceComparer.Instance);

		internal _BidirectionalDictionary()
		{
			GCUtils.Collected += _Collected;
		}
		protected override void Dispose(bool disposing)
		{
			if (disposing)
				GCUtils.Collected -= _Collected;

			base.Dispose(disposing);
		}
		private void _Collected()
		{
			if (WasDisposed)
				return;

			try
			{
				lock(DisposeLock)
				{
					_dictionary1 = new Dictionary<long,object>(_dictionary1);


					var oldHelper = _helpers;
					var newHelper = new Dictionary<Thread, Helper>(ReferenceComparer.Instance);

					foreach(var pair in oldHelper)
					{
						var thread = pair.Key;
						var value = pair.Value;

						if (thread.IsAlive)
						{
							newHelper.Add(thread, value);

							value._dictionary2 = new Dictionary<object,long>(value._dictionary2, ReferenceComparer.Instance);
						}
					}

					_helpers = newHelper;
				}
			}
			catch
			{
			}
		}

		public object Get(long id)
		{
			object result;

			lock(DisposeLock)
				if (!_dictionary1.TryGetValue(id, out result))
					throw new RemotingException("Can't find Id referenced from remote side... how?");

			return result;
		}
		public _ReferenceOrWrapped GetOrWrap(object obj)
		{
			long id;
			lock(DisposeLock)
			{
				var helper = _helpers.GetOrCreateValue(Thread.CurrentThread);

				if (helper._dictionary2.TryGetValue(obj, out id))
					return new _Reference { Id = id };

				id = Interlocked.Increment(ref _idGenerator);
				_dictionary1.Add(id, obj);
				helper._dictionary2.Add(obj, id);
					
				var type = obj.GetType();
				if (type.IsSubclassOf(typeof(Delegate)))
				{
					_WrappedDelegate wd = new _WrappedDelegate();
					wd.DelegateType = type;
					wd.Id = id;
					return wd;
				}

				_Wrapped wrapped = new _Wrapped();
				wrapped.Id = id;
				wrapped.InterfaceTypes = type.GetFinalInterfaces();
				return wrapped;
			}
		}

		internal void RemoveIds(long[] ids)
		{
			foreach(long id in ids)
			{
				object obj;
						
				if (!_dictionary1.TryGetValue(id, out obj))
					throw new RemotingException("Trying to remove a reference that does not exist!");

				_dictionary1.Remove(id);

				foreach(var helper in _helpers.Values)
				{
					long otherId;
					if (helper._dictionary2.TryGetValue(obj, out otherId))
					{
						if (otherId == id)
						{
							helper._dictionary2.Remove(obj);
							break;
						}
					}
				}
			}
		}

		internal void Clear()
		{
			lock(DisposeLock)
			{
				_dictionary1.Clear();

				foreach(var helper in _helpers.Values)
					helper._dictionary2.Clear();
			}
		}
	}
}
